# -*- coding: utf-8 -*-
# @Time: 2022/9/11 22:03
# @File: table.py
import sys
from random import sample
from itertools import product

import pygame

from setting import *


class Table:

    def __init__(self):
        self.init_table()
        # 右上角的时间
        self.run_time = pygame.time.get_ticks()
        self.window = pygame.display.get_surface()
        self.mines_number = MAX_MINES_NUMBER
        self.game_state = "run"  # run fail success
        self.bomb_cell = []

        # 爆炸的图片
        self.bomb = self.load_cell_image("../image/bomb.png")
        # 地雷的图片
        self.mines = self.load_cell_image("../image/mines.png")
        # 红旗的图片
        self.flag = self.load_cell_image("../image/flag.png")
        # 表情的图片
        self.smile, self.smile_rect = self.load_face("../image/smile.png")
        self.cool, self.cool_rect = self.load_face("../image/cool.png")
        self.sad, self.sad_rect = self.load_face("../image/sad.png")
        # 炸弹数量的图片
        self.mines_number_surfaces = [
            pygame.image.load(f"../font/red/{i}.png") for i in range(10)]
        # 用时数字的图片
        self.time_number_surfaces = [
            pygame.image.load(f"../font/yellow/{i}.png").convert_alpha() for i in range(10)]
        # 单元格字体
        self.cell_font = pygame.font.Font(
            "../font/cell_font.ttf", CELL_FONT_SIZE)

        self.table = self.init_table()

    @classmethod
    def load_cell_image(cls, file_path):
        """
        加载单元格的图片
        :param file_path: 图片路径
        :return: Surface
        """
        image = pygame.image.load(file_path).convert_alpha()
        return pygame.transform.smoothscale(image, (CELL_SIZE, CELL_SIZE))

    @classmethod
    def load_face(cls, file_path):
        """
        加载表情图片
        :param file_path: 图片路径
        :return: Surface, Rect
        """
        surface = pygame.image.load(file_path)
        new_surface = pygame.transform.smoothscale(
            surface, (CELL_SIZE * 2, CELL_SIZE * 2))
        rect = new_surface.get_rect(center=(WINDOW_WIDTH // 2, TABLE_TOP // 2))
        return new_surface, rect

    @classmethod
    def init_table(cls):
        """
        初始化表格数据
        :return:
        """
        cell_list = range(TABLE_ROW * TABLE_COLUMN)
        mines_index = sample(cell_list, MAX_MINES_NUMBER)
        mines_table = list()
        # 生成地雷
        for y in range(TABLE_ROW):
            row_list = list()
            for x in range(TABLE_COLUMN):
                index = y * TABLE_COLUMN + x
                cell = {
                    "value": -1 if index in mines_index else 0,
                    "check_time": -CHECK_TIME,
                    "state": "cover"  # cover, check, show, flag, doubt
                }
                row_list.append(cell)
            mines_table.append(row_list)
        # 计算每个单元格周围的地雷数量
        for x in range(TABLE_ROW):
            for y in range(TABLE_COLUMN):
                if mines_table[x][y]["value"] == -1:
                    continue
                mines_table[x][y]["value"] = sum(
                    1 for i in range(max(0, x - 1), min(TABLE_ROW, x + 2))
                    for j in range(max(0, y - 1), min(TABLE_COLUMN, y + 2))
                    if mines_table[i][j]["value"] == -1)
        return mines_table

    def check_zero(self):
        """
        检测展示的空格的周围是否有覆盖的空格，如果存在，将其状态改为展示
        :return:
        """
        for x, y in product(range(TABLE_ROW), range(TABLE_COLUMN)):
            if not (self.table[x][y]["value"] == 0 and
                    self.table[x][y]["state"] == "show"):
                continue
            for i, j in product(range(max(0, x - 1), min(TABLE_ROW, x + 2)),
                                range(max(0, y - 1), min(TABLE_COLUMN, y + 2))):
                self.table[i][j]["state"] = "show"

    def check_around(self, x, y):
        """
        检测数字周围的格子，如果旗子数量等于数字，展示周围数字，判断游戏是否结束
        如果旗子数量不等于数字，将覆盖状态设置为检测状态
        :return:
        """
        flag = 0
        bomb = False
        bomb_cell = self.bomb_cell
        check = True

        # 计算周围旗子数量，判断是否有未标记的地雷
        for i, j in product(range(max(0, x - 1), min(TABLE_ROW, x + 2)),
                            range(max(0, y - 1), min(TABLE_COLUMN, y + 2))):
            if self.table[i][j]["state"] == "flag":
                flag += 1
            if self.table[i][j]["value"] == -1 and \
                    self.table[i][j]["state"] != "flag":
                bomb = True
                bomb_cell = [i, j]
        # 旗子数量等于数字
        if flag == self.table[x][y]["value"]:
            if bomb:
                self.game_state = "fail"
                self.bomb_cell = bomb_cell
                return
            else:
                check = False
        # 修改周围格子的状态
        check_time = pygame.time.get_ticks()
        for i, j in product(range(max(0, x - 1), min(TABLE_ROW, x + 2)),
                            range(max(0, y - 1), min(TABLE_COLUMN, y + 2))):
            if self.table[i][j]["state"] == "cover":
                if check:
                    self.table[i][j]["state"] = "check"
                    self.table[i][j]["check_time"] = check_time
                else:
                    self.table[i][j]["state"] = "show"

    def input(self):
        """
        检测用户输入
        :return:
        """
        # 获取鼠标坐标，基于窗口左上角的像素坐标
        mouse_x, mouse_y = pygame.mouse.get_pos()
        # 接收鼠标事件
        mouse_left, mouse_right = False, False
        for event in pygame.event.get():
            # 程序退出
            if event.type == pygame.QUIT:
                pygame.quit()
                sys.exit()
            # 判断鼠标按键抬起
            if event.type == pygame.MOUSEBUTTONUP:
                mouse_left = event.button == 1
                mouse_right = event.button == 3
        # 如果鼠标没点击不处理
        if not (mouse_left or mouse_right):
            return
        if mouse_left and self.smile_rect.collidepoint(mouse_x, mouse_y):
            # 点击表情重新开始
            self.run_time = pygame.time.get_ticks()
            self.mines_number = MAX_MINES_NUMBER
            self.game_state = "run"
            self.bomb_cell = []
            self.table = self.init_table()
            return
        # 鼠标点击了表格外不处理
        start_x = (WINDOW_WIDTH - TABLE_COLUMN * CELL_SIZE) // 2
        start_y = TABLE_TOP
        end_x, end_y = WINDOW_WIDTH - start_x, start_y + TABLE_ROW * CELL_SIZE
        if mouse_x < start_x or mouse_x >= end_x or \
                mouse_y < start_y or mouse_y >= end_y:  # end处需要大于等于
            return
        # 将像素坐标转换为数组坐标
        y = (mouse_x - start_x) // CELL_SIZE
        x = (mouse_y - start_y) // CELL_SIZE
        state = self.table[x][y]["state"]
        # 右击处理覆盖、标记和疑问状态的单元格
        state_list = ["cover", "flag", "doubt"]
        # 同时按下时，优先处理右键的点击
        if mouse_right and state in state_list:
            if state == "flag":  # 待查找地雷数加一
                self.mines_number += 1
            self.table[x][y]["state"] = state_list[  # 轮转单元格的状态
                (state_list.index(state) + 1) % len(state_list)]
            if self.table[x][y]["state"] == "flag":
                self.mines_number -= 1  # 待查找地雷数减一
        # 左击处理覆盖和展示状态的单元格
        if mouse_left and state in ["cover", "show"]:
            if state == "show":
                self.check_around(x, y)
            # 点到地雷
            elif self.table[x][y]["value"] == -1:
                self.game_state = "fail"
                self.bomb_cell = [x, y]
            # 没点到地雷
            else:
                self.table[x][y]["state"] = "show"

    def test(self):
        mouse_x, mouse_y = pygame.mouse.get_pos()
        start_x = (WINDOW_WIDTH - TABLE_COLUMN * CELL_SIZE) // 2
        start_y = TABLE_TOP
        y = (mouse_x - start_x) // CELL_SIZE
        x = (mouse_y - start_y) // CELL_SIZE
        end_x, end_y = WINDOW_WIDTH - start_x, start_y + TABLE_ROW * CELL_SIZE
        if mouse_x < start_x or mouse_x >= end_x or \
                mouse_y < start_y or mouse_y >= end_y:
            return
        surface = self.cell_font.render(
            str(self.table[x][y]["value"]), False, "red")
        rect = surface.get_rect(topleft=(0, 0))
        self.window.blit(surface, rect)

    def draw(self):
        """
        绘制表格图像
        :return:
        """
        start_x = (WINDOW_WIDTH - TABLE_COLUMN * CELL_SIZE) // 2
        start_y = TABLE_TOP

        # 游戏失败
        if self.game_state == "fail":
            # 画出哭脸
            pygame.draw.rect(self.window, BACKGROUND, self.cool_rect)
            self.window.blit(self.sad, self.sad_rect)
            # 画出所有的地雷
            for x, y in product(range(TABLE_ROW), range(TABLE_COLUMN)):
                left = start_x + y * CELL_SIZE
                top = start_y + x * CELL_SIZE
                cell = self.table[x][y]
                rect = pygame.Rect(left, top, CELL_SIZE, CELL_SIZE)
                if cell["value"] != -1:
                    continue
                # 覆盖原来的东西
                pygame.draw.rect(self.window, CELL_COLOR, rect)
                # 画出爆炸点
                if self.bomb_cell and self.bomb_cell == [x, y]:
                    self.window.blit(self.bomb, rect)
                else:  # 画炸弹
                    self.window.blit(self.mines, rect)
            self.draw_grid(start_x, start_y)
            return

        # 游戏成功，画个笑脸
        if self.game_state == "success":
            # 改掉前面的笑脸
            pygame.draw.rect(self.window, BACKGROUND, self.cool_rect)
            self.window.blit(self.cool, self.cool_rect)
            return

        # 覆盖原来的样式
        pygame.draw.rect(self.window, BACKGROUND,
                         (0, 0, WINDOW_WIDTH, WINDOW_HEIGHT))

        # 画表情
        self.window.blit(self.smile, self.smile_rect)
        # 画剩余地雷数量，两位数，从左到右
        mines_number = str(self.mines_number).zfill(2)
        mines_number_width, mines_number_height = \
            self.mines_number_surfaces[0].get_size()
        rect1 = pygame.Rect(start_x, (start_y - mines_number_height) // 2,
                            mines_number_width, mines_number_height)
        rect2 = pygame.Rect(rect1.x + mines_number_width, rect1.y,
                            mines_number_width, mines_number_height)
        self.window.blit(
            self.mines_number_surfaces[int(mines_number[0])], rect1)
        self.window.blit(
            self.mines_number_surfaces[int(mines_number[1])], rect2)
        # 画运行时间，三位数，从右到左
        run_time = str(min(
            (pygame.time.get_ticks() - self.run_time) // 1000, 999)).zfill(3)
        time_number_width, time_number_height = \
            self.time_number_surfaces[0].get_size()
        rect1 = pygame.Rect(
            start_x + TABLE_COLUMN * CELL_SIZE - time_number_width,
            (start_y - time_number_height) // 2,
            time_number_width, time_number_height)
        rect2 = pygame.Rect(rect1.x - time_number_width, rect1.y,
                            time_number_width, time_number_height)
        rect3 = pygame.Rect(rect2.x - time_number_width, rect1.y,
                            time_number_width, time_number_height)
        self.window.blit(self.time_number_surfaces[int(run_time[-1])], rect1)
        self.window.blit(self.time_number_surfaces[int(run_time[-2])], rect2)
        self.window.blit(self.time_number_surfaces[int(run_time[-3])], rect3)

        # 绘制表格
        for x, y in product(range(TABLE_ROW), range(TABLE_COLUMN)):
            left = start_x + y * CELL_SIZE
            top = start_y + x * CELL_SIZE
            cell = self.table[x][y]
            rect = pygame.Rect(left, top, CELL_SIZE, CELL_SIZE)
            if cell["state"] == "show":
                # 绘制格子背景色
                pygame.draw.rect(self.window, BACKGROUND, rect)
                if cell["value"] > 0:  # 写数字
                    font_surface = self.cell_font.render(
                        str(cell["value"]), False,
                        NUMBER_COLORS[cell["value"] - 1])
                    font_rect = font_surface.get_rect(center=rect.center)
                    self.window.blit(font_surface, font_rect)
                continue
            if cell["state"] == "check":
                past_time = pygame.time.get_ticks() - cell["check_time"]
                if past_time <= CHECK_TIME:
                    pygame.draw.rect(self.window, CELL_CHECK_COLOR, rect)
                    continue
                else:
                    self.table[x][y]["state"] = "cover"
            # 绘制格子背景色
            pygame.draw.rect(self.window, CELL_COLOR, rect)
            if cell["state"] == "flag":
                self.window.blit(self.flag, rect)
            elif cell["state"] == "doubt":
                font_surface = self.cell_font.render("?", False, BACKGROUND)
                font_rect = font_surface.get_rect(center=rect.center)
                self.window.blit(font_surface, font_rect)

        self.draw_grid(start_x, start_y)

    def draw_grid(self, start_x, start_y):
        """
        画网格线
        :param start_x: 网格左上角的x像素坐标
        :param start_y: 网格左上角的y像素坐标
        :return:
        """
        # 画网格横线
        for i in range(TABLE_ROW + 1):
            start_point = (start_x, start_y + i * CELL_SIZE)
            end_point = (start_x + CELL_SIZE * TABLE_COLUMN, start_point[1])
            pygame.draw.line(self.window, CELL_CHECK_COLOR,
                             start_point, end_point)
        # 画网格竖线
        for i in range(TABLE_COLUMN + 1):
            start_point = (start_x + i * CELL_SIZE, start_y)
            end_point = (start_point[0], start_y + CELL_SIZE * TABLE_ROW)
            pygame.draw.line(self.window, CELL_CHECK_COLOR,
                             start_point, end_point)

    def check_success(self):
        cover_number = 0
        for x, y in product(range(TABLE_ROW), range(TABLE_COLUMN)):
            if self.table[x][y]["state"] in ["cover", "check", "flag", "doubt"]:
                cover_number += 1
        if cover_number == MAX_MINES_NUMBER:
            self.game_state = "success"

    def update(self):
        """
        更新逻辑
        :return:
        """
        self.input()
        self.check_zero()
        self.draw()
        self.check_success()
